/*--------------------------------------------------------------------------------
|                                     Recursive Locker is a part of Thread Utils |
|                                                             recursive_locker.h |
|                                                    (c) 2012-2013 Mist Poryvaev |
|                                                e-mail: mist.poryvaev@gmail.com |
--------------------------------------------------------------------------------*/

#ifndef _RECURSIVE_LOCKER_H_
#define _RECURSIVE_LOCKER_H_

#include <QMutex>
#include <QReadWriteLock>
#include <QMap>
#include <QThread>
#include <QIODevice>

#if defined(_WIN32_WINNT) && defined(WIN_MUTEX)

#include <windows.h>

class QWinMutex
{
public:
	inline QWinMutex() { ::InitializeCriticalSection(& cs); }
	inline ~QWinMutex() { ::DeleteCriticalSection(& cs); }
	inline void lock() { ::EnterCriticalSection(& cs); }
	inline void unlock() { ::LeaveCriticalSection(& cs); }
	inline bool tryLock() { return ::TryEnterCriticalSection(& cs); }
private:
	CRITICAL_SECTION cs;
	QWinMutex(const QWinMutex &) {}
	QWinMutex & operator = (const QWinMutex &) { return * this; }
};

class QWinMutexLocker
{
public:
	inline QWinMutexLocker(QWinMutex * m): wm(m) { wm->lock(); }
	inline ~QWinMutexLocker() { wm->unlock(); }
private:
	QWinMutex * wm;
	QWinMutexLocker() {}
	QWinMutexLocker(const QWinMutexLocker &) {}
	QWinMutexLocker & operator = (const QWinMutexLocker &) { return * this; }
};

#endif

class RecursiveLocker
{
	RecursiveLocker(const RecursiveLocker &): d(0), usage_mode((UsageMode)-1) {}
	RecursiveLocker & operator = (const RecursiveLocker &) { return (* this); }
public:

#if defined(_WIN32_WINNT) && defined(WIN_MUTEX)
	typedef QWinMutex Mutex;
	typedef QWinMutexLocker MutexLocker;
#else
	typedef QMutex Mutex;
	typedef QMutexLocker MutexLocker;
#endif

	inline RecursiveLocker(): d(new Data()), other(0), m(new Mutex()), usage_mode(MutexUsage), enabled(true)
	{
	}
	inline RecursiveLocker(RecursiveLocker * _other, QIODevice::OpenMode _mode = QIODevice::WriteOnly): d(0), other(_other), m(0), usage_mode(LockerUsage), enabled(false) 
	{
		if (_mode == QIODevice::ReadOnly)
		{
			other->lockForRead();
		}
		else
		{
			other->lockForWrite();
		}
	}
	inline ~RecursiveLocker()
	{
		if (other)
		{
			other->unlock();
		}
		else
		{
			unlock();
		}
		if (m)
		{
			delete m;
		}
		if (d)
		{
			delete d;
		}
	}
	inline bool tryLockForRead()
	{
		if (!m)
		{
			return (false);
		}
		if (!enabled)
		{
			return true;
		}
		m->lock();
		bool _not_locked_in_this_thread = false, _locked = false;
		const Qt::HANDLE _tid = QThread::currentThreadId();
		int & _r_count = d->r_count[_tid];
		if (_r_count == 0 && d->w_count[_tid] == 0)
		{
			_locked = d->locker.tryLockForRead();
			_not_locked_in_this_thread = !_locked;
		}
		if (!_not_locked_in_this_thread)
		{
			_r_count++;
			_locked = true;
		}
		m->unlock();
		return _locked;
	}
	inline void lockForRead()
	{
		if (!m)
		{
			return;
		}
		if (!enabled)
		{
			return;
		}
		m->lock();
		bool _not_locked_in_this_thread = false;
		const Qt::HANDLE _tid = QThread::currentThreadId();
		int & _r_count = d->r_count[_tid];
		if (_r_count == 0 && d->w_count[_tid] == 0)
		{
			_not_locked_in_this_thread = !d->locker.tryLockForRead();
		}
		_r_count++;
		m->unlock();
		if (_not_locked_in_this_thread)
		{
			d->locker.lockForRead();
		}
	}
	inline bool tryLockForWrite()
	{
		if (!m)
		{
			return (false);
		}
		if (!enabled)
		{
			return true;
		}
		m->lock();
		bool _not_locked_in_this_thread = false, _locked = false;
		const Qt::HANDLE _tid = QThread::currentThreadId();
		int & _w_count = d->w_count[_tid];
		if (_w_count == 0)
		{
			if (d->r_count[_tid] > 0)
			{	
				d->locker.unlock();
			}
			_locked = d->locker.tryLockForWrite();
			_not_locked_in_this_thread = !_locked;
		}
		if (!_not_locked_in_this_thread)
		{
			_w_count++;
			_locked = true;
		}
		m->unlock();
		return _locked;
	}
	inline bool tryLock()
	{
		return tryLockForWrite();
	}
	inline void lockForWrite()
	{
		if (!m)
		{
			return;
		}
		if (!enabled)
		{
			return;
		}
		m->lock();
		bool _not_locked_in_this_thread = false;
		const Qt::HANDLE _tid = QThread::currentThreadId();
		int & _w_count = d->w_count[_tid];
		if (_w_count == 0)
		{
			if (d->r_count[_tid] > 0)
			{	
				d->locker.unlock();
			}
			_not_locked_in_this_thread = !d->locker.tryLockForWrite();
		}
		_w_count++;
		m->unlock();
		if (_not_locked_in_this_thread)
		{
			d->locker.lockForWrite();
		}
	}
	inline void lock()
	{
		lockForWrite();
	}
	inline void unlock()
	{
		if (!m)
		{
			return;
		}
		if (!enabled)
		{
			return;
		}
		m->lock();
		const Qt::HANDLE _tid = QThread::currentThreadId();
		bool _was_locked = false;
		int & _r_count = d->r_count[_tid];
		int & _w_count = d->w_count[_tid];
		bool r_contains_tid = (_r_count > 0);
		bool w_contains_tid = (_w_count > 0);
		if (r_contains_tid)
		{
			if (--_r_count == 0)
			{
				_was_locked = true;
				r_contains_tid = false;
			}
		}
		else
		if (w_contains_tid)
		{
			if (--_w_count == 0)
			{
				_was_locked = true;
				w_contains_tid = false;
			}
		}
		if (!r_contains_tid && !w_contains_tid && _was_locked)
		{
			d->locker.unlock();
		}
		if (r_contains_tid && d->r_count.count() > 8)
		{
			for (QMap<Qt::HANDLE, int>::iterator _i = d->r_count.begin(); _i != d->r_count.end();)
			{
				if (_i.value() == 0 && _i.key() != _tid)
				{
					_i = d->r_count.erase(_i);
				}
				else
				{
					_i++;
				}
			}
		}
		if (w_contains_tid && d->w_count.count() > 8)
		{
			for (QMap<Qt::HANDLE, int>::iterator _i = d->w_count.begin(); _i != d->w_count.end();)
			{
				if (_i.value() == 0 && _i.key() != _tid)
				{
					_i = d->w_count.erase(_i);
				}
				else
				{
					_i++;
				}
			}
		}
		m->unlock();
	}
	enum State {Unlocked = 0, LockedForRead = 1, LockedForWrite = 2, SharedLocked = LockedForRead, ExclusiveLocked = LockedForWrite};
	inline State processState() const
	{
		if (!m)
		{
			return (Unlocked);
		}
		if (!enabled)
		{
			return (Unlocked);
		}
		m->lock();
		State _state = Unlocked;
		if (!d->w_count.isEmpty())
		{
			_state = LockedForWrite;
		}
		else
		if (!d->r_count.isEmpty())
		{
			_state = LockedForRead;
		}
		m->unlock();
		return (_state);
	}
	inline State threadState() const
	{
		if (!m)
		{
			return (Unlocked);
		}
		if (!enabled)
		{
			return (Unlocked);
		}
		m->lock();
		const Qt::HANDLE _tid = QThread::currentThreadId();
		State _state = Unlocked;
		if (d->w_count.contains(_tid))
		{
			_state = LockedForWrite;
		}
		else
		if (d->r_count.contains(_tid))
		{
			_state = LockedForRead;
		}
		m->unlock();
		return (_state);
	}
	inline void setEnabled(bool state) { enabled = state; }
	inline bool isEnabled() const { return enabled; }
	enum UsageMode {MutexUsage = 0, LockerUsage = 1};
	inline const UsageMode usageMode() const { return usage_mode; }
private:
	Mutex * m;
	int w, r;
	const UsageMode usage_mode;
	bool enabled;
	struct Data
	{
		QMap<Qt::HANDLE, int> r_count, w_count;
		QReadWriteLock locker;
		Data() {}
	};
	Data * d;
	RecursiveLocker * other;
};

#include "thread_utils_defs.h"

#endif